package com.bestv.search.management.datasyn.spider;

import com.bestv.search.common.cache.SystemCache;
import com.bestv.search.common.dataSource.DataSourceKey;
import com.bestv.search.common.dataSource.DataSourceKeyUtil;
import com.bestv.search.common.model.ProgramSyncStatus;
import com.bestv.search.common.service.ProgramSyncStatusManager;
import com.bestv.search.common.util.FtpUtil;
import com.bestv.search.common.util.PropertyStrategyWrapper;
import com.bestv.search.common.util.StringConst;
import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;
import net.sf.json.util.PropertySetStrategy;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public abstract class SpiderSyncer {
    // Sets buffer size to 0.5M
    protected static final int BUFFER_SIZE = 512000;
    protected static ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{
            "applicationContext-dao.xml", "applicationContext-resource.xml", "applicationContext-service.xml",
            "security.xml"});
    protected static String lineSeparator = System.getProperties().getProperty("line.separator");
    protected Logger logger = Logger.getLogger(SpiderSyncer.class);
    protected ProgramSyncStatusManager programSyncStatusManager;
    protected ProgramSyncStatus status;
    protected SystemCache systemCache;
    protected SimpleDateFormat format;
    private Date thisSyncSuccessTime = null;

    // Properties for access FTP server
    private String host;
    private String userName;
    private String password;
    private String pubFolder;
    private String ftpFileSeparator;

    public SpiderSyncer() {
        programSyncStatusManager = (ProgramSyncStatusManager) context.getBean("programSyncStatusManager");
        systemCache = (SystemCache) context.getBean("systemCache");
        format = new SimpleDateFormat(StringConst.DATETIME_FORMAT);
        host = systemCache.getFtpServer().get(StringConst.HOST);
        userName = systemCache.getFtpServer().get(StringConst.USERNAME);
        password = systemCache.getFtpServer().get(StringConst.PASSWORD);
        pubFolder = systemCache.getFtpServer().get(StringConst.PUBFOLDER);
        ftpFileSeparator = systemCache.getFtpServer().get(StringConst.FTPFILESEPARATOR);
    }

    protected ProgramSyncStatus getProgramSyncStatus(String sysName, int serverNo, String fieldName) {
        // local site code equals to local data source Key.
        String siteCode = systemCache.getLocalDataSourceKey();
        DataSourceKey key = DataSourceKeyUtil.convert(siteCode);

        return programSyncStatusManager.get(key, siteCode, sysName, serverNo, fieldName);
    }

    protected void updateSyncStatus(ProgramSyncStatus status) {
        // local site code equals to local data source Key.
        String siteCode = systemCache.getLocalDataSourceKey();
        DataSourceKey key = DataSourceKeyUtil.convert(siteCode);

        // Save to DB
        programSyncStatusManager.saveOrUpdate(key, status);
    }

    protected String getFileFullPath(String type, String site, Date lastSyncSuccessTime, String fileName) {
        FtpUtil ftp = new FtpUtil();
        try {
            ftp.connect(host, userName, password, StringConst.UTF_8);
            String ftpDir = pubFolder;
            if (!ftpDir.endsWith(ftpFileSeparator)) {
                ftpDir += ftpFileSeparator;
            }
            ftpDir = ftpDir + type + ftpFileSeparator + site + ftpFileSeparator;
            // Gets all folders that lie in ftpDir.
            FTPFile[] listFiles = ftp.listFiles(ftpDir);
            // Gets last created folder
            String lastFolderName = getLastInfosFolder(listFiles);
            if (lastFolderName == null) {
                logger.info("Don't find any " + site + StringConst.SPACE + type + " info folder");
                this.thisSyncSuccessTime = null;
                return null;
            } else {
                if (lastSyncSuccessTime == null) {
                    ftpDir += lastFolderName + ftpFileSeparator;
                } else {
                    String syncSuccessTime = format.format(lastSyncSuccessTime);
                    if (syncSuccessTime.compareTo(lastFolderName) >= 0) {
                        logger.info("Don't find any new " + site + StringConst.SPACE + type
                                + " info in system search_spider @" + new Date());
                        return null;
                    } else {
                        ftpDir += lastFolderName + ftpFileSeparator;
                    }
                }

                // Check isRunning.txt whether is exist, if exist that says
                // spider thread is running.
                if (ftp.existFile(ftpDir, StringConst.ISRUNNING_FLAG_FILE)) {
                    logger.info(site + StringConst.SPACE + type
                            + " info is syncing in search_spider, wait for it finished");
                    this.thisSyncSuccessTime = null;
                    return null;
                } else {
                    try {
                        this.thisSyncSuccessTime = format.parse(lastFolderName);
                    } catch (ParseException e) {
                        logger.info("Folder name is incorrectly.");
                        e.printStackTrace();
                        return null;
                    }

                    // download to local cache folder and always override it.
                    String localFolder = getCacheFolder();
                    if (ftp.downloadFile(ftpDir, getCacheFolder(), fileName, true)) {
                        return localFolder + fileName;
                    } else {
                        return null;
                    }
                }
            }
        } catch (Exception e) {
            logger.info("Download file " + fileName + " failed from ftp server.");
            logger.info(e);
            return null;
        } finally {
            try {
                ftp.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String getLastInfosFolder(FTPFile[] listFiles) {
        if (listFiles != null && listFiles.length > 0) {
            Arrays.sort(listFiles, new Comparator<FTPFile>() {
                @Override
                public int compare(FTPFile f1, FTPFile f2) {
                    return f2.getName().compareTo(f1.getName());
                }
            });
            return listFiles[0].getName();
        } else {
            return null;
        }
    }

    protected abstract <T, K> T convert(K sourceObject, T destObject);

    protected abstract <T, K> void saveOrUpdateToDB(K sourceObj, List<T> dbObj);

    @SuppressWarnings("unchecked")
    protected <T, K> void sync(T dbObj, K jsonObj, String filePath) {
        List<T> list = new ArrayList<T>();
        FileInputStream inStream = null;
        InputStreamReader inReader = null;
        BufferedReader br = null;
        try {
            // Read stream
            inStream = new FileInputStream(new File(filePath));
            inReader = new InputStreamReader(inStream, StringConst.UTF_8);
            br = new BufferedReader(inReader);
            String line = null;
            while ((line = br.readLine()) != null) {
                if (line != null && !line.equals(StringConst.EMPTY)) {
                    K info = null;
                    try {
                        JSONObject jsonDetails = JSONObject.fromObject(line);
                        JsonConfig detailcfg = new JsonConfig();
                        detailcfg.setPropertySetStrategy(new PropertyStrategyWrapper(PropertySetStrategy.DEFAULT));
                        detailcfg.setRootClass(jsonObj.getClass());
                        info = (K) JSONObject.toBean(jsonDetails, detailcfg);
                    } catch (Exception e) {
                        logger.warn("Convert json string to object failed, ignore it, " + line);
                        logger.warn(e);
                    }
                    if (info != null) {
                        // Convert to dbObject
                        try {
                            dbObj = (T) (Class.forName(dbObj.getClass().getName()).newInstance());
                        } catch (Exception e) {
                            e.printStackTrace();
                            logger.info("Init class " + dbObj.getClass().getName() + " failed." + e.getMessage());
                        }
                        dbObj = convert(info, dbObj);

                        // Add object to list
                        list.add(dbObj);

                        // Save to DB
                        if (list.size() >= 100) {
                            saveOrUpdateToDB(jsonObj, list);
                            list = new ArrayList<T>();
                        }
                    }
                }
            }

            // Save objects to DB
            saveOrUpdateToDB(jsonObj, list);

        } catch (IOException e) {
            logger.info("Read file " + filePath + " failed.");
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                // Ignore exception
                logger.warn(e);
            }
        }
    }

    private String getCacheFolder() {
        String cacheFolder = SpiderSyncer.class.getClassLoader().getResource(File.separator).getPath()
                + StringConst.CACHE;
        File folder = new File(cacheFolder);
        if (!folder.exists()) {
            folder.mkdir();
        }
        return cacheFolder + File.separator;
    }

    protected ProgramSyncStatus getSyncStatus(String sysName, String field, String syncMessage) {
        if (status == null) {
            status = new ProgramSyncStatus();
            status.setSysName(sysName);
            status.setSiteCode(systemCache.getLocalDataSourceKey());
            status.setServerNo(1);
            status.setFieldName(field);
        }
        if (this.thisSyncSuccessTime != null) {
            status.setSyncSuccessTime(this.thisSyncSuccessTime);
        }
        status.setSyncMessage(syncMessage);
        status.setLastSyncTime(new Date());
        status.setUpdateTime(new Date());

        return status;
    }
}